package com.sb.hot;

import cn.hutool.core.date.SystemClock;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.system.RuntimeInfo;
import com.sb.hot.controller.Index;
import com.sb.hot.controller.MyClassLoaderCustom;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.CachedIntrospectionResults;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.system.JavaVersion;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.beans.Introspector;
import java.lang.management.ClassLoadingMXBean;
import java.lang.management.ManagementFactory;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URLClassLoader;
import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * lxk
 * java  -cp .; org.springframework.boot.loader.JarLauncher
 * lxk
 * Thread.currentThread().getId() +"  "+Thread.currentThread()+"  "+Thread.currentThread().getContextClassLoader()
 * lxk
 * 看下 ,main刚刚启动时,加载进的类个数. 和种类
 * jar.class 是用到再加载?   -Dverbose:class
 * target.classess 是一开始就加载?
 * 为什么本地可以覆盖jar中class ?  appclassloader java.home.path 中classes类路径是在jar包前?
 *
 * 先 hutoll 后 class;     运行不  输出  ######### SystemClock SystemClock #########!
 * java  -Dspring.application.admin.enabled=true -Dfile.encoding=UTF-8 -classpath "D:\itsoft\java8\jre\lib\charsets.jar;D:\itsoft\java8\jre\lib\deploy.jar;D:\itsoft\java8\jre\lib\ext\access-bridge-64.jar;D:\itsoft\java8\jre\lib\ext\cldrdata.jar;D:\itsoft\java8\jre\lib\ext\dnsns.jar;D:\itsoft\java8\jre\lib\ext\jaccess.jar;D:\itsoft\java8\jre\lib\ext\jfxrt.jar;D:\itsoft\java8\jre\lib\ext\localedata.jar;D:\itsoft\java8\jre\lib\ext\nashorn.jar;D:\itsoft\java8\jre\lib\ext\sunec.jar;D:\itsoft\java8\jre\lib\ext\sunjce_provider.jar;D:\itsoft\java8\jre\lib\ext\sunmscapi.jar;D:\itsoft\java8\jre\lib\ext\sunpkcs11.jar;D:\itsoft\java8\jre\lib\ext\zipfs.jar;D:\itsoft\java8\jre\lib\javaws.jar;D:\itsoft\java8\jre\lib\jce.jar;D:\itsoft\java8\jre\lib\jfr.jar;D:\itsoft\java8\jre\lib\jfxswt.jar;D:\itsoft\java8\jre\lib\jsse.jar;D:\itsoft\java8\jre\lib\management-agent.jar;D:\itsoft\java8\jre\lib\plugin.jar;D:\itsoft\java8\jre\lib\resources.jar;D:\itsoft\java8\jre\lib\rt.jar;D:\itsoft\java8\lib\tools.jar;E:\repertory_maven\org\slf4j\slf4j-api\1.7.30\slf4j-api-1.7.30.jar;E:\repertory_maven\cn\hutool\hutool-all\5.5.7\hutool-all-5.5.7.jar;D:\itsoft\workspace_idea_self\00d-to-study\hot\target\classes;E:\repertory_maven\org\springframework\boot\spring-boot\2.4.2\spring-boot-2.4.2.jar;E:\repertory_maven\org\springframework\spring-core\5.3.3\spring-core-5.3.3.jar;E:\repertory_maven\org\springframework\spring-jcl\5.3.3\spring-jcl-5.3.3.jar;E:\repertory_maven\org\springframework\spring-context\5.3.3\spring-context-5.3.3.jar;E:\repertory_maven\org\springframework\boot\spring-boot-autoconfigure\2.4.2\spring-boot-autoconfigure-2.4.2.jar;E:\repertory_maven\org\springframework\boot\spring-boot-starter-web\2.4.2\spring-boot-starter-web-2.4.2.jar;E:\repertory_maven\org\springframework\boot\spring-boot-starter\2.4.2\spring-boot-starter-2.4.2.jar;E:\repertory_maven\org\springframework\boot\spring-boot-starter-logging\2.4.2\spring-boot-starter-logging-2.4.2.jar;E:\repertory_maven\ch\qos\logback\logback-classic\1.2.3\logback-classic-1.2.3.jar;E:\repertory_maven\ch\qos\logback\logback-core\1.2.3\logback-core-1.2.3.jar;E:\repertory_maven\org\apache\logging\log4j\log4j-to-slf4j\2.13.3\log4j-to-slf4j-2.13.3.jar;E:\repertory_maven\org\apache\logging\log4j\log4j-api\2.13.3\log4j-api-2.13.3.jar;E:\repertory_maven\org\slf4j\jul-to-slf4j\1.7.30\jul-to-slf4j-1.7.30.jar;E:\repertory_maven\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;E:\repertory_maven\org\yaml\snakeyaml\1.27\snakeyaml-1.27.jar;E:\repertory_maven\org\springframework\boot\spring-boot-starter-json\2.4.2\spring-boot-starter-json-2.4.2.jar;E:\repertory_maven\com\fasterxml\jackson\core\jackson-databind\2.11.4\jackson-databind-2.11.4.jar;E:\repertory_maven\com\fasterxml\jackson\core\jackson-annotations\2.11.4\jackson-annotations-2.11.4.jar;E:\repertory_maven\com\fasterxml\jackson\core\jackson-core\2.11.4\jackson-core-2.11.4.jar;E:\repertory_maven\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.11.4\jackson-datatype-jdk8-2.11.4.jar;E:\repertory_maven\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.11.4\jackson-datatype-jsr310-2.11.4.jar;E:\repertory_maven\com\fasterxml\jackson\module\jackson-module-parameter-names\2.11.4\jackson-module-parameter-names-2.11.4.jar;E:\repertory_maven\org\springframework\boot\spring-boot-starter-tomcat\2.4.2\spring-boot-starter-tomcat-2.4.2.jar;E:\repertory_maven\org\apache\tomcat\embed\tomcat-embed-core\9.0.41\tomcat-embed-core-9.0.41.jar;E:\repertory_maven\org\glassfish\jakarta.el\3.0.3\jakarta.el-3.0.3.jar;E:\repertory_maven\org\apache\tomcat\embed\tomcat-embed-websocket\9.0.41\tomcat-embed-websocket-9.0.41.jar;E:\repertory_maven\org\springframework\spring-web\5.3.3\spring-web-5.3.3.jar;E:\repertory_maven\org\springframework\spring-beans\5.3.3\spring-beans-5.3.3.jar;E:\repertory_maven\org\springframework\spring-webmvc\5.3.3\spring-webmvc-5.3.3.jar;E:\repertory_maven\org\springframework\spring-aop\5.3.3\spring-aop-5.3.3.jar;E:\repertory_maven\org\springframework\spring-expression\5.3.3\spring-expression-5.3.3.jar;D:\itsoft\IntelliJ IDEA 2020.3\lib\idea_rt.jar" com.sb.hot.App  -Dx1
 * 先 class 后 hutoll;  运行     输出  ######### SystemClock SystemClock #########!
 * java  -Dspring.application.admin.enabled=true -Dfile.encoding=UTF-8 -classpath "D:\itsoft\java8\jre\lib\charsets.jar;D:\itsoft\java8\jre\lib\deploy.jar;D:\itsoft\java8\jre\lib\ext\access-bridge-64.jar;D:\itsoft\java8\jre\lib\ext\cldrdata.jar;D:\itsoft\java8\jre\lib\ext\dnsns.jar;D:\itsoft\java8\jre\lib\ext\jaccess.jar;D:\itsoft\java8\jre\lib\ext\jfxrt.jar;D:\itsoft\java8\jre\lib\ext\localedata.jar;D:\itsoft\java8\jre\lib\ext\nashorn.jar;D:\itsoft\java8\jre\lib\ext\sunec.jar;D:\itsoft\java8\jre\lib\ext\sunjce_provider.jar;D:\itsoft\java8\jre\lib\ext\sunmscapi.jar;D:\itsoft\java8\jre\lib\ext\sunpkcs11.jar;D:\itsoft\java8\jre\lib\ext\zipfs.jar;D:\itsoft\java8\jre\lib\javaws.jar;D:\itsoft\java8\jre\lib\jce.jar;D:\itsoft\java8\jre\lib\jfr.jar;D:\itsoft\java8\jre\lib\jfxswt.jar;D:\itsoft\java8\jre\lib\jsse.jar;D:\itsoft\java8\jre\lib\management-agent.jar;D:\itsoft\java8\jre\lib\plugin.jar;D:\itsoft\java8\jre\lib\resources.jar;D:\itsoft\java8\jre\lib\rt.jar;D:\itsoft\java8\lib\tools.jar;E:\repertory_maven\org\slf4j\slf4j-api\1.7.30\slf4j-api-1.7.30.jar;D:\itsoft\workspace_idea_self\00d-to-study\hot\target\classes;E:\repertory_maven\cn\hutool\hutool-all\5.5.7\hutool-all-5.5.7.jar;E:\repertory_maven\org\springframework\boot\spring-boot\2.4.2\spring-boot-2.4.2.jar;E:\repertory_maven\org\springframework\spring-core\5.3.3\spring-core-5.3.3.jar;E:\repertory_maven\org\springframework\spring-jcl\5.3.3\spring-jcl-5.3.3.jar;E:\repertory_maven\org\springframework\spring-context\5.3.3\spring-context-5.3.3.jar;E:\repertory_maven\org\springframework\boot\spring-boot-autoconfigure\2.4.2\spring-boot-autoconfigure-2.4.2.jar;E:\repertory_maven\org\springframework\boot\spring-boot-starter-web\2.4.2\spring-boot-starter-web-2.4.2.jar;E:\repertory_maven\org\springframework\boot\spring-boot-starter\2.4.2\spring-boot-starter-2.4.2.jar;E:\repertory_maven\org\springframework\boot\spring-boot-starter-logging\2.4.2\spring-boot-starter-logging-2.4.2.jar;E:\repertory_maven\ch\qos\logback\logback-classic\1.2.3\logback-classic-1.2.3.jar;E:\repertory_maven\ch\qos\logback\logback-core\1.2.3\logback-core-1.2.3.jar;E:\repertory_maven\org\apache\logging\log4j\log4j-to-slf4j\2.13.3\log4j-to-slf4j-2.13.3.jar;E:\repertory_maven\org\apache\logging\log4j\log4j-api\2.13.3\log4j-api-2.13.3.jar;E:\repertory_maven\org\slf4j\jul-to-slf4j\1.7.30\jul-to-slf4j-1.7.30.jar;E:\repertory_maven\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;E:\repertory_maven\org\yaml\snakeyaml\1.27\snakeyaml-1.27.jar;E:\repertory_maven\org\springframework\boot\spring-boot-starter-json\2.4.2\spring-boot-starter-json-2.4.2.jar;E:\repertory_maven\com\fasterxml\jackson\core\jackson-databind\2.11.4\jackson-databind-2.11.4.jar;E:\repertory_maven\com\fasterxml\jackson\core\jackson-annotations\2.11.4\jackson-annotations-2.11.4.jar;E:\repertory_maven\com\fasterxml\jackson\core\jackson-core\2.11.4\jackson-core-2.11.4.jar;E:\repertory_maven\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.11.4\jackson-datatype-jdk8-2.11.4.jar;E:\repertory_maven\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.11.4\jackson-datatype-jsr310-2.11.4.jar;E:\repertory_maven\com\fasterxml\jackson\module\jackson-module-parameter-names\2.11.4\jackson-module-parameter-names-2.11.4.jar;E:\repertory_maven\org\springframework\boot\spring-boot-starter-tomcat\2.4.2\spring-boot-starter-tomcat-2.4.2.jar;E:\repertory_maven\org\apache\tomcat\embed\tomcat-embed-core\9.0.41\tomcat-embed-core-9.0.41.jar;E:\repertory_maven\org\glassfish\jakarta.el\3.0.3\jakarta.el-3.0.3.jar;E:\repertory_maven\org\apache\tomcat\embed\tomcat-embed-websocket\9.0.41\tomcat-embed-websocket-9.0.41.jar;E:\repertory_maven\org\springframework\spring-web\5.3.3\spring-web-5.3.3.jar;E:\repertory_maven\org\springframework\spring-beans\5.3.3\spring-beans-5.3.3.jar;E:\repertory_maven\org\springframework\spring-webmvc\5.3.3\spring-webmvc-5.3.3.jar;E:\repertory_maven\org\springframework\spring-aop\5.3.3\spring-aop-5.3.3.jar;E:\repertory_maven\org\springframework\spring-expression\5.3.3\spring-expression-5.3.3.jar;D:\itsoft\IntelliJ IDEA 2020.3\lib\idea_rt.jar" com.sb.hot.App  -Dx2
 * lxk: 结合 App 中的   SystemClock(和是否静态变量无关) 的 加载顺序,
 * 说明  先从 main class 先执行, 再看 -classpath 去找 class , jar在前就先找到jar 中的原始类, 默认target 在前(毕竟是启动的目标),所以可以覆盖jar中类
 *
 *
 * lxk
 * .e.DevToolsPropertyDefaultsPostProcessor : Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
 *
 * @author liuxiongkang
 */
@SpringBootApplication
@RestController
@RequestMapping("app")
public class App {

    private static final Logger LOG = LoggerFactory.getLogger(App.class);
    private static final SystemClock systemClock = new SystemClock(10);

    private static String[] args;
    private static ConfigurableApplicationContext context;

    public static void main(String[] args) {
        Thread.currentThread().setContextClassLoader(new MyClassLoaderCustom());
        ThreadUtil.execAsync(() -> {
            System.out.println("@@@@@ 当前线程上下文 ClassLoader=======" + Thread.currentThread().getContextClassLoader());
            System.out.println("@@@@@ 当前主类 ClassLoader=======" + App.class.getClassLoader());
            App.args = args;
            App.context = SpringApplication.run(App.class, args);
        });
        // try {
        //     // System.out.println(systemClock);
        //     App.args = args;
        //     App.context = SpringApplication.run(App.class, args);
        //     logInfo();
        // } catch (Exception e) {
        //     if (e.getClass().getName().endsWith("SilentExitException")) {
        //         System.out.println("@@@@@@@");
        //     }
        //     e.printStackTrace();
        // }
    }

    private static void logInfo() throws ClassNotFoundException {
        System.out.println("版本=======" + context.getBean(Index.class).getVersion());
        System.out.println("========= SystemClock.class.getClassLoader:" + SystemClock.class.getClassLoader());
        Class.forName(SystemClock.class.getName());
        ClassLoadingMXBean clazz = ManagementFactory.getClassLoadingMXBean();
        System.out.printf("=======class Loaded: %s    Total: %s   Unloaded: %s%n", clazz.getLoadedClassCount(), clazz.getTotalLoadedClassCount(), clazz.getUnloadedClassCount());
        classLoader();
        RuntimeInfo runtimeInfo = new RuntimeInfo();
        System.out.printf("=======Max Memory:  %s    Total Memory:  %s    Free Memory: %s    Usable Memory:  %s", FileUtil.readableFileSize(runtimeInfo.getMaxMemory()), FileUtil.readableFileSize(runtimeInfo.getTotalMemory()), FileUtil.readableFileSize(runtimeInfo.getFreeMemory()), FileUtil.readableFileSize(runtimeInfo.getUsableMemory()));
        System.out.println();
    }

    private static void classLoader() {
        ClassLoader classLoader = App.class.getClassLoader();
        List<String> dataList = new ArrayList<>();
        while (null != classLoader) {
            dataList.add(classLoader.toString());
            classLoader = classLoader.getParent();
        }
        dataList.add("      ");
        classLoader = Thread.currentThread().getContextClassLoader();
        while (null != classLoader) {
            dataList.add(classLoader.toString());
            classLoader = classLoader.getParent();
        }
        System.out.println("=======classLoader:" + dataList);
    }

    //TODO App类中 静态字段 和 注解 引用的类  的 类加载器会变化吗?
    @GetMapping("/refresh/{appName}")
    public String restart(@PathVariable("appName") String appName) {
        synchronized (App.class) {
            ExecutorService threadPool = new ThreadPoolExecutor(1, 1, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1), new ThreadPoolExecutor.DiscardOldestPolicy());
            threadPool.execute(() -> {
                context.close();
                try {
                    cleanupCaches();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println("=======准备重启为:" + appName + " " + System.getProperty("tkey1") + " " + System.getenv("tkey2"));
                System.setProperty("=======spring.application.name", appName);
                try {
                    URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
                    URLClassLoader urlClassLoader = new URLClassLoader(classLoader.getURLs(), Thread.currentThread().getContextClassLoader().getParent());
                    //TODO 一定要设置线程上下文类加载器,因为Spring Boot 在加载一些类的使用的就是这个线程上下文类加载器,而默认的线程上下文类加载器等于ClassLoader.getSystemClassLoader(),如果两个类加载器加载同一个类文件就会认为是两个类,会无法赋值,Spring Boot也起不来
                    Thread.currentThread().setContextClassLoader(urlClassLoader);
                    Class<?> aClass = urlClassLoader.loadClass(App.class.getName());
                    Class<?> bClass = urlClassLoader.loadClass(SpringApplication.class.getName());
                    Method main = bClass.getMethod("run", Class.class, String[].class);
                    context = (ConfigurableApplicationContext) main.invoke(null, aClass, new String[0]);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                //context = SpringApplication.run(App.class, args);
                try {
                    logInfo();
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            });
            threadPool.shutdown();
            return "version:" + context.getBean(Index.class).getVersion();
        }
    }

    @GetMapping("/info")
    public String info() throws ClassNotFoundException {
        synchronized (App.class) {
            logInfo();
            return "version:" + context.getBean(Index.class).getVersion() + "   " + context.getEnvironment().getProperty("spring.application.name");
        }
    }

    @GetMapping("/testAddBean")
    public String testReturn() {
        synchronized (App.class) {
            System.out.println("test 当前应用为:" + context.getEnvironment().getProperty("spring.application.name"));
            context.getBeanFactory().registerSingleton(context.getBean(Index.class).getVersion(), context.getBean(Index.class).getVersion());
            List<String> dataList = new ArrayList<>();
            for (int i = 0; i < 10000; i++) {
                try {
                    String intern = (i + "").intern();
                    dataList.add(intern);
                    //System.out.println("intern:"+intern);
                    LoggerFactory.getLogger(intern).debug(intern);
                    context.getBeanFactory().registerSingleton("v" + intern, intern);
                } catch (Exception e) {
                    e.printStackTrace();
                    System.out.println("dataList:" + dataList);
                }
            }
            return "test version:" + context.getBean(Index.class).getVersion() + "   appName:" + context.getEnvironment().getProperty("spring.application.name");
        }
    }

    private void cleanupCaches() throws Exception {
        Introspector.flushCaches();
        cleanupKnownCaches();
        forceReferenceCleanup();
        System.gc();
        System.runFinalization();
    }

    private void forceReferenceCleanup() {
        try {
            final List<long[]> memory = new LinkedList<>();
            while (true) {
                memory.add(new long[102400]);
            }
        } catch (OutOfMemoryError ex) {
            // Expected
        }
    }

    private void cleanupKnownCaches() throws Exception {
        // Whilst not strictly necessary it helps to cleanup soft reference caches
        // early rather than waiting for memory limits to be reached
        ResolvableType.clearCache();
        cleanCachedIntrospectionResultsCache();
        ReflectionUtils.clearCache();
        clearAnnotationUtilsCache();
        if (!JavaVersion.getJavaVersion().isEqualOrNewerThan(JavaVersion.NINE)) {
            clear("com.sun.naming.internal.ResourceManager", "propertiesCache");
        }
    }

    private void cleanCachedIntrospectionResultsCache() throws Exception {
        clear(CachedIntrospectionResults.class, "acceptedClassLoaders");
        clear(CachedIntrospectionResults.class, "strongClassCache");
        clear(CachedIntrospectionResults.class, "softClassCache");
    }

    private void clearAnnotationUtilsCache() throws Exception {
        try {
            AnnotationUtils.clearCache();
        } catch (Throwable ex) {
            clear(AnnotationUtils.class, "findAnnotationCache");
            clear(AnnotationUtils.class, "annotatedInterfaceCache");
        }
    }

    private void clear(String className, String fieldName) {
        try {
            clear(Class.forName(className), fieldName);
        } catch (Exception ex) {
            LOG.debug("Unable to clear field " + className + " " + fieldName, ex);
        }
    }

    private void clear(Class<?> type, String fieldName) {
        try {
            Field field = type.getDeclaredField(fieldName);
            field.setAccessible(true);
            Object instance = field.get(null);
            if (instance instanceof Set) {
                ((Set<?>) instance).clear();
            }
            if (instance instanceof Map) {
                ((Map<?, ?>) instance).clear();
            }
        } catch (Exception ex) {
            LOG.debug("Unable to clear field " + type + " " + fieldName, ex);
        }
    }
}
